package com.gioneco.util.esCondition;

import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 条件拼接工具类
 * @author dongjialin
 * @since 2022-06-01
 */
public class Criteria {

    /**
     * 必要条件缓存
     */
    public Map<String,Object> mustParamMap = new ConcurrentHashMap<String,Object>();

    /**
     * 模糊条件缓存
     */
    public Map<String,Object> likeParamMap = new ConcurrentHashMap<String,Object>();

    /**
     * 排序字段
     */
    public Map<String,Object> orderParamMap = new ConcurrentHashMap<String,Object>();

    /**
     * 分页字段
     */
    public Map<String,Object> pagerParamMap = new ConcurrentHashMap<String,Object>();

    /**
     * 指定字段查询
     */
    public Set<String> withFieldsParamSet = new HashSet<>();

    /**
     * ES 范围查询对象
     */
    public RangeQueryBuilder rangeQueryBuilder = null;

    /**
     * 分组条件组合
     */
    public AbstractAggregationBuilder<?> aggregationBuilder = null;

    /**
     * 分组列名
     */
    public String aggregationFieldName;

    public Integer current;

    public Integer size;

    private String key;

    private Object value;


    public Criteria(String key){
        this.key = key;
    }

    public static <T,R> Criteria where(EsFunction<T,R> function,boolean ... isKeyWord){
        String key = null;
        if(ObjectUtils.isEmpty(function)){
            return new Criteria(key);
        }
        try{
            key = getLambdaKey(function,isKeyWord);
        }catch (Exception e){
            e.printStackTrace();
        }
        return new Criteria(key);
    }

    /**
     * 条件查询
     * @param condition true 表示拼接条件
     * @param isKeyWord 是否分词查询
     */
    public static <T,R> Criteria where(boolean condition,EsFunction<T,R> function,boolean ... isKeyWord){
        String key = null;
        if(condition){
            try{
                key = getLambdaKey(function,isKeyWord);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return new Criteria(key);
    }

    /**
     * 查询所有
     */
    public static Criteria All(){
        return new Criteria(null);
    }


    /**
     * 日期比较 >=
     */
    public Criteria gte(LocalDate value){
        if(ObjectUtils.isEmpty(value)){
            return this;
        }
        this.value = value;
        if(!StringUtils.isEmpty(key)){
            if(rangeQueryBuilder==null){
                rangeQueryBuilder = new RangeQueryBuilder(key);
                rangeQueryBuilder.gte(value.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
            }else{
                rangeQueryBuilder.gte(value.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
            }
        }
        return this;
    }


    /**
     * 日期比较 >=
     */
    public Criteria timeGte(String value){
        if(ObjectUtils.isEmpty(value)){
            return this;
        }
        this.value = value;
        if(!StringUtils.isEmpty(key)){
            if(rangeQueryBuilder==null){
                rangeQueryBuilder = new RangeQueryBuilder(key+".keyword");
                rangeQueryBuilder.gte(value);
            }else{
                rangeQueryBuilder.gte(value);
            }
        }
        return this;
    }

    /**
     * 日期比较 <=
     */
    public Criteria lte(LocalDate value){
        if(ObjectUtils.isEmpty(value)){
            return this;
        }
        this.value = value;
        if(!StringUtils.isEmpty(key)){
            if(rangeQueryBuilder==null){
                rangeQueryBuilder = new RangeQueryBuilder(key);
                rangeQueryBuilder.lte(value.plusDays(1).atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
            }else{
                rangeQueryBuilder.lte(value.plusDays(1).atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
            }
        }
        return this;
    }

    public Criteria is(Object value){
        if(ObjectUtils.isEmpty(value)){
            return this;
        }
        this.value = value;
        if(!StringUtils.isEmpty(key))
        mustParamMap.put(key,value);
        return this;
    }

    public Criteria like(Object value){
        if(ObjectUtils.isEmpty(value)){
            return this;
        }
        this.value = value;
        if(!StringUtils.isEmpty(key)) {
            likeParamMap.put(key,value);
        }
        return this;
    }

    public <T,R>Criteria order(EsFunction<T,R> function, SortOrder order,boolean isKeyWord){
        try {
            key = getLambdaKey(function,isKeyWord);
            orderParamMap.put(key,order);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;
    }

    public <T,R>Criteria and(EsFunction<T,R> function){
        this.key= null;
        this.value = null;
        return getParams(function);
    }

    public <T,R>Criteria and(boolean condition,EsFunction<T,R> function){
        this.key= null;
        this.value = null;
        if(condition){
            return getParams(function);
        }
        return this;
    }

    private static String resolveFieldName(String getMethodName) {
        if (getMethodName.startsWith("get")) {
            getMethodName = getMethodName.substring(3);
        } else if (getMethodName.startsWith("is")) {
            getMethodName = getMethodName.substring(2);
        }

        return firstToLowerCase(getMethodName);
    }

    private static String firstToLowerCase(String param) {
        return StringUtils.isEmpty(param) ? "" : param.substring(0, 1).toLowerCase() + param.substring(1);
    }


    public <T,R>Criteria getParams(EsFunction<T,R> function){
        try{
            Method method = function.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda)method.invoke(function);
            String getter = serializedLambda.getImplMethodName();
            key = resolveFieldName(getter);
        }catch (Exception e){
            e.printStackTrace();
        }
        return this;
    }


    public static <T,R>String getLambdaKey(EsFunction<T, R> function, boolean ... isKeyWord) throws Exception {
        String key = null;
        Method method = function.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(Boolean.TRUE);
        SerializedLambda serializedLambda = (SerializedLambda)method.invoke(function);
        String getter = serializedLambda.getImplMethodName();
        key = resolveFieldName(getter);
        if(isKeyWord!=null && isKeyWord.length>0&&isKeyWord[0]){
            key = key+".keyword";
        }
        return key;
    }

    /**
     * 查询指定字段
     */
    public <T,R>Criteria withFields(EsFunction<T, R>... fields){
        if(!ObjectUtils.isEmpty(fields)){
            for (EsFunction<T, R> func : fields) {
                String fieldName = null;
                try {
                    fieldName = getLambdaKey(func,null);
                    withFieldsParamSet.add(fieldName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return this;
    }

    /**
     * 文本聚合查询
     */
    public <T,R>Criteria groupByDoc(EsFunction<T, R> field) {
        String fieldName = null;
        try {
            fieldName = getLambdaKey(field,null);
            aggregationBuilder = AggregationBuilders.terms(fieldName).field(fieldName+".keyword").size(Integer.MAX_VALUE);
            aggregationFieldName=fieldName;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;
    }

    /**
     * 时间聚合查询
     * 目前写死 yyyy-MM-dd 需要使用函数可参考  https://blog.csdn.net/qq_40675243/article/details/123718644
     */
    public <T,R>Criteria groupByDate(EsFunction<T, R> field) {
        String fieldName = null;
        try {
            fieldName = getLambdaKey(field,null);
            Script script = new Script("def domain = doc['systemTime.keyword'].value;def domain1 =domain.substring(0, 11);return domain1");
            aggregationBuilder = AggregationBuilders.terms(fieldName).field(fieldName + ".keyword").script(script).size(Integer.MAX_VALUE);
            aggregationFieldName=fieldName;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;
    }

    /**
     * 分页封装
     * @param current 当前页数
     * @param size  每页显示条数
     */
    public Criteria page(Integer current, Integer size) {
        pagerParamMap.put("current",(current-1));
        pagerParamMap.put("size",size);
        this.current = current;
        this.size  = size;
        return this;
    }
}
